package com.wechat.service.cmds;

import java.lang.reflect.Method;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

import com.swak.exception.FluxInvokeException;
import com.swak.reactivex.threads.Context;
import com.swak.reactivex.threads.Contexts;
import com.swak.router.HandlerFunction;
import com.swak.router.RouterFunction;
import com.swak.router.RouterFunctions;
import com.swak.utils.StringUtils;
import com.swak.vertx.invoker.GenericFluxInvoker;
import com.wechat.annotation.Cmd;
import com.wechat.config.CmdBean;

/**
 * 命令执行
 * 
 * @author 618lf
 */
@SuppressWarnings("unchecked")
public class CmdFunction implements HandlerFunction<CmdRoute> {

	private final Cmd cmd;
	protected final Object service;
	protected final Method method;
	protected final GenericFluxInvoker invoker;

	public CmdFunction(CmdBean bean) {
		Object service = bean.getService();
		Cmd job = bean.getCmd();

		Method executeMethod = null;
		Method[] methods = service.getClass().getMethods();
		for (Method method : methods) {
			if (StringUtils.isNotBlank(job.method()) && method.getName().equals(job.method())) {
				executeMethod = method;
			}
		}

		this.cmd = job;
		this.service = service;
		this.method = executeMethod;
		this.invoker = new GenericFluxInvoker(this.service);
	}

	@Override
	public Object handle(CmdRoute request) {
		try {
			Object[] args = new Object[] { request.getChannel(), request.getCommand() };
			return this.invoke(args);
		} catch (Throwable e) {
			RuntimeException error = new FluxInvokeException(
					String.format("Failed to Invoke [Service:{%s} - Method: {%s}].", service.getClass().getName(),
							method.getName()),
					e.getCause() != null ? e.getCause() : e);
			throw error;
		}
	}

	/**
	 * 执行相关方法
	 * 
	 * @throws Throwable
	 */
	protected Object invoke(Object[] args) throws Throwable {
		Context context = Contexts.currentContext();
		Object result = this.invoker.doInvoke(this.method, args);
		if (result != null && result instanceof CompletionStage && context != null) {
			CompletionStage<Object> resultFuture = (CompletionStage<Object>) result;
			CompletableFuture<Object> future = new CompletableFuture<>();
			resultFuture.whenComplete((v, e) -> {
				context.runOnContext(() -> {
					if (e != null) {
						future.completeExceptionally(e);
					} else {
						future.complete(v);
					}
				});
			});
			return future;
		}
		return result;
	}

	public RouterFunction<CmdRoute> route() {
		return RouterFunctions.path(this.cmd.command().name(), this);
	}
}